<!DOCTYPE html>
<!--
  The MIT License (MIT)

  Copyright (c) 2017 Tarek Sherif

  Permission is hereby granted, free of charge, to any person obtaining a copy of
  this software and associated documentation files (the "Software"), to deal in
  the Software without restriction, including without limitation the rights to
  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  the Software, and to permit persons to whom the Software is furnished to do so,
  subject to the following conditions:

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-->
<html>
<head>
    <title>PicoGL.js: Particles</title>
    <meta charset="utf-8">
    <script src="utils/gl-matrix.js"></script>
    <script src="../build/picogl.js"></script>
    <script src="utils/utils.js"></script>
    <link rel="stylesheet" href="../site/css/picogl-example.css"> 
    <style>
        #particle-controls {
            position: absolute;
            bottom: 20px;
            right: 30px;
        }
    </style>
</head>
<body>
    <div id="example-title">
        PicoGL.js Example: Particles
        <div>
            <a href="https://github.com/tsherif/picogl.js/blob/master/examples/particles.html">Source code</a>
        </div>
    </div>
    <div id="particle-controls">
        <button onclick="setDrawRange(50000)">50,000 particles</button>
        <button onclick="setDrawRange(100000)">100,000 particles</button>
        <button onclick="setDrawRange(200000)">200,000 particles</button>
    </div>
    <canvas id="gl-canvas"></canvas>
    <script id="main-vs" type="x-shader/vs">
        #version 300 es

        layout(std140) uniform;

        layout(location=0) in vec3 aPosition;
        layout(location=1) in vec3 aVelocity;
        layout(location=2) in vec3 aColor;

        uniform Mass {
            vec4 mass1Position;
            vec4 mass2Position;
            vec4 mass3Position;
            float mass1Factor;
            float mass2Factor;
            float mass3Factor;
        };
    
        out vec3 vPosition;
        out vec3 vVelocity;
        out vec3 vColor;
        void main() {
            vec3 position = aPosition;
            vec3 velocity = aVelocity;

            vec3 massVec = mass1Position.xyz - position;
            float massDist2 = max(0.01, dot(massVec, massVec));
            vec3 acceleration = mass1Factor * normalize(massVec) / massDist2;

            massVec = mass2Position.xyz - position;
            massDist2 = max(0.01, dot(massVec, massVec));
            acceleration += mass2Factor * normalize(massVec) / massDist2;

            massVec = mass3Position.xyz - position;
            massDist2 = max(0.01, dot(massVec, massVec));
            acceleration += mass3Factor * normalize(massVec) / massDist2;

            velocity += acceleration;
            velocity *= 0.9999;

            vPosition = position + velocity;
            vVelocity = velocity;


            vColor = aColor;
            gl_PointSize = 2.0;
            gl_Position = vec4(position, 1.0);
        }
    </script>
    <script id="main-fs" type="x-fragment-shader">
        #version 300 es
        precision highp float;

        in vec3 vColor;

        out vec4 fragColor;
        void main() {
            float alpha = 0.3;
            fragColor = vec4(vColor * alpha, alpha);
        }
    </script>
    <script>
        utils.addTimerElement();

        var canvas = document.getElementById("gl-canvas");

        if (!utils.testWebGL2()) {
            console.error("WebGL 2 not available");
            document.body.innerHTML = "This example requires WebGL 2 which is unavailable on this system."
        }

        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
        
        var app = PicoGL.createApp(canvas)
        .clearColor(0.0, 0.0, 0.0, 1.0)
        .blend()
        .blendFunc(PicoGL.ONE, PicoGL.ONE_MINUS_SRC_ALPHA);

        var timer = app.createTimer();

        // PROGRAM
        var vsSource = document.getElementById("main-vs").text.trim();
        var fsSource = document.getElementById("main-fs").text.trim();
        var program = app.createProgram(vsSource, fsSource, ["vPosition", "vVelocity"]);

        // GEOMETRY DATA
        var NUM_PARTICLES = 200000;
        var positionData = new Float32Array(NUM_PARTICLES * 3);
        var colorData = new Uint8Array(NUM_PARTICLES * 3);

        for (var i = 0; i < NUM_PARTICLES; ++i) {
            var vec3i = i * 3;

            positionData[vec3i] = Math.random() * 2 - 1;
            positionData[vec3i + 1] = Math.random() * 2 - 1;
            positionData[vec3i + 2] = Math.random() * 2 - 1;

            colorData[vec3i] =     Math.floor(Math.random() * 256);
            colorData[vec3i + 1] = Math.floor(Math.random() * 256);
            colorData[vec3i + 2] = Math.floor(Math.random() * 256);
        }

        // INPUT AND OUTPUT POSITION AND VELOCITY BUFFERS
        var positions1 = app.createVertexBuffer(PicoGL.FLOAT, 3, positionData);
        var velocities1 = app.createVertexBuffer(PicoGL.FLOAT, 3, positionData.length);
        var positions2 = app.createVertexBuffer(PicoGL.FLOAT, 3, positionData.length);
        var velocities2 = app.createVertexBuffer(PicoGL.FLOAT, 3, positionData.length);
        
        // COLOR BUFFER NOT INVOLVED IN TRANSFORM FEEDBACK
        var colors = app.createVertexBuffer(PicoGL.UNSIGNED_BYTE, 3, colorData);

        // INPUT AND OUTPUT VERTEX ARRAYS
        var vertexArray1 = app.createVertexArray()
        .vertexAttributeBuffer(0, positions1)
        .vertexAttributeBuffer(1, velocities1)
        .vertexNormalizedAttributeBuffer(2, colors);

        var vertexArray2 = app.createVertexArray()
        .vertexAttributeBuffer(0, positions2)
        .vertexAttributeBuffer(1, velocities2)
        .vertexNormalizedAttributeBuffer(2, colors);

        // TRANSFORM FEEDBACK OBJECTS
        var transformFeedback1 = app.createTransformFeedback()
        .feedbackBuffer(0, positions1)
        .feedbackBuffer(1, velocities1);

        var transformFeedback2 = app.createTransformFeedback()
        .feedbackBuffer(0, positions2)
        .feedbackBuffer(1, velocities2);

        // UNIFORM BUFFER FOR SIMULATION PARAMETERS
        var massUniforms = app.createUniformBuffer([
            PicoGL.FLOAT_VEC4,
            PicoGL.FLOAT_VEC4,
            PicoGL.FLOAT_VEC4,
            PicoGL.FLOAT,
            PicoGL.FLOAT,
            PicoGL.FLOAT
        ]).set(0, vec3.fromValues(
            Math.random() * 2.0 - 1.0,
            Math.random() * 2.0 - 1.0,
            Math.random() * 2.0 - 1.0
        )).set(1, vec3.fromValues(
            Math.random() * 2.0 - 1.0,
            Math.random() * 2.0 - 1.0,
            Math.random() * 2.0 - 1.0
        )).set(2, vec3.fromValues(
            Math.random() * 2.0 - 1.0,
            Math.random() * 2.0 - 1.0,
            Math.random() * 2.0 - 1.0
        ))
        .set(3, Math.random() / 30000)
        .set(4, Math.random() / 30000)
        .set(5, Math.random() / 30000)
        .update();

        window.onresize = function() {
            app.resize(window.innerWidth, window.innerHeight);

            mat4.perspective(projMatrix, Math.PI / 2, app.width / app.height, 0.1, 10.0);
            mat4.multiply(viewProjMatrix, projMatrix, viewMatrix);
        };

        var drawCall1 = app.createDrawCall(program, vertexArray1, PicoGL.POINTS)
        .transformFeedback(transformFeedback2)
        .uniformBlock("Mass", massUniforms)
        .elementCount(50000);

        var drawCall2 = app.createDrawCall(program, vertexArray2, PicoGL.POINTS)
        .transformFeedback(transformFeedback1)
        .uniformBlock("Mass", massUniforms)
        .elementCount(50000);

        function setDrawRange(count) {
            drawCall1.elementCount(count);
            drawCall2.elementCount(count);
        }

        var currentDrawCall = drawCall1;
        function draw() {      
            if (timer.ready()) {
                utils.updateTimerElement(timer.cpuTime, timer.gpuTime);
            }

            timer.start();

            // DRAW
            app.clear();
            currentDrawCall.draw();

            currentDrawCall = currentDrawCall === drawCall1 ? drawCall2 : drawCall1;

            timer.end();

            requestAnimationFrame(draw);
        }

        requestAnimationFrame(draw);

    </script>
    <a href="https://github.com/tsherif/picogl.js" id="github-ribbon"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a>
    <script src="../site/js/iframe.js"></script>
</body>
</html>
